Skip to content

fix(desktop): seed nest Claude settings with buzz CLI allowlist - #5263

Open
thomaspblock wants to merge 2 commits into
mainfrom
wintermute/nest-claude-buzz-allowlist
Open

fix(desktop): seed nest Claude settings with buzz CLI allowlist#5263
thomaspblock wants to merge 2 commits into
mainfrom
wintermute/nest-claude-buzz-allowlist

Conversation

@thomaspblock

@thomaspblock thomaspblock commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Fixes #5262. Related: #5260 (same root cause, independently reported twice today).

Problem

Since #4609 the ACP harness rejects every session/request_permission (fail closed), and Desktop-managed sessions run in dontAsk mode with no in-app approval prompt. Claude Code raises a permission request for any Bash pattern not pre-authorized in its settings, so managed Claude agents lost access to the bundled buzz CLI: they appear online but cannot reliably read channels or send replies.

Fix

Seed and conservatively merge <nest>/.claude/settings.local.json with Bash(buzz) and Bash(buzz:*) during ensure_nest_at.

  • Local settings are honored in fresh untrusted workspaces; project-level .claude/settings.json grants are ignored until interactive workspace trust is accepted.
  • Existing settings and unknown keys are preserved; only missing rules are appended.
  • Invalid or unexpectedly shaped JSON is left untouched.
  • Writes remain atomic and idempotent.
  • Existing symlinked .claude directories and settings files are rejected before read/write, and regression tests prove external targets remain unchanged.
  • User-global ~/.claude configuration is never touched.

This is temporary Buzz-owned compatibility behavior. It restores Buzz read/reply only; it does not restore broader unattended shell/tool capability. Reconcile or remove this workspace grant when Desktop permission approval UI ships in #5106.

Known limitation

Allow rules match single commands only: buzz feed get runs, but buzz ... | head or buzz ...; echo $? still raises a request and is rejected under dontAsk.

Testing

Exact pushed head: 91838616a99e8941b9fb47ce910e6dc45052ad9d

  • Focused regression suite: 8 claude_settings tests passed (5 original merge tests plus project-settings-untouched and both symlink escape cases).
  • Pre-push hook passed on the exact pushed head: branch skew, Desktop checks/typecheck/tests, full Rust tests, clippy, and full Desktop Tauri workspace tests.
  • Real-runtime release acceptance used a fresh untrusted nest whose pre-existing settings.local.json included unrelated model/allow/deny values. Through the actual claude-agent-acp ACP path, the session mode was set to dontAsk and permission requests were fail-closed, matching the managed harness contract.
    • Adapter: @zed-industries/claude-agent-acp 0.18.0
    • Adapter-bundled Claude Code: 2.1.45
    • buzz --help: succeeded (Usage: buzz [OPTIONS] <COMMAND>)
    • Signed buzz messages send: {"accepted":true,"event_id":"121c1936e1aa50740900322d1b9f9a460295068268454678df0ff8fa4ef31d38","mention_pubkeys":[],"message":""}
    • Adapter stderr: empty; no trust prompt or permission request/denial occurred.

Since #4609 the ACP harness rejects every session/request_permission
(fail closed) and desktop managed sessions run in dontAsk mode with no
in-app approval prompt. Claude Code raises a permission request for any
Bash pattern not pre-authorized in its settings, so managed Claude
agents lost all access to the bundled `buzz` CLI — they could not read
channels or send replies (#5262, #5260).

Claude Code evaluates permissions.allow rules before the permission
mode, so a project-level allow rule survives dontAsk. Seed
`<nest>/.claude/settings.json` with `Bash(buzz)` and `Bash(buzz:*)` at
nest creation: managed sessions start with the nest root as cwd and the
ACP adapter passes settingSources ["user", "project", "local"], so the
rules apply to every managed Claude agent without touching the user's
own ~/.claude/settings.json.

The merge is conservative: existing settings are preserved, only
missing rules are appended, unparseable or unexpectedly-shaped files
are left untouched, and the write is atomic (temp file + persist,
matching refresh_skill_md_if_stale). Idempotent across launches.

This deliberately does not reopen the blanket auto-approval that #4609
removed: only the platform's own CLI is pre-authorized, agent-side,
which #4609 explicitly scopes as outside the harness guarantee. The
broader fix (harness permission policy with desktop approval cards) is
in flight as #5106; this unblocks agents until it lands.

Known limitation: allow rules match single commands only — `buzz feed
get` runs, but `buzz ... | head` or `buzz ...; echo $?` still raises a
request and is rejected under dontAsk.

Fixes #5262.

Co-authored-by: Thomas Petersen <thomasp@squareup.com>
Signed-off-by: Thomas Petersen <thomasp@squareup.com>
@thomaspblock
thomaspblock requested a review from a team as a code owner August 7, 2026 23:29
@colbymaloy

Copy link
Copy Markdown

yea need this one in asap boys

@apatrida

apatrida commented Aug 8, 2026

Copy link
Copy Markdown

yea need this one in asap boys

Yeah, spent about 90 minutes on this one today until narrowed it down to the conflicting startup and config. Anyways, reported, and being handled it appears.

@LucasMoskun

Copy link
Copy Markdown

I tested this patch strategy against Claude Code 2.1.220 on Buzz Desktop 0.5.6, macOS, with a locally managed agent connected to a self-hosted relay. The proposed <nest>/.claude/settings.json location did not unblock buzz after a full agent restart.

The fresh headless Claude process emitted:

Ignoring 2 permissions.allow entries from .claude/settings.json: this workspace has not been trusted.

This matches the current Claude Code workspace-trust behavior: project permissions.allow rules grant capability and are ignored until the workspace trust dialog is accepted. A Buzz ACP-managed session cannot present that dialog. See https://code.claude.com/docs/en/permissions#project-allow-rules-and-workspace-trust

I then moved the exact same rules:

{
  "permissions": {
    "allow": [
      "Bash(buzz)",
      "Bash(buzz:*)"
    ]
  }
}

to <nest>/.claude/settings.local.json. Under the same cwd, --permission-mode dontAsk, and --setting-sources user,project,local, a fresh Claude invocation executed buzz --help and returned permission_denials: [].

Recommendation: seed and conservatively merge <nest>/.claude/settings.local.json instead, and add an integration check using a current Claude Code version with an untrusted fresh nest. Programmatically marking the nest trusted in the user-wide trust registry seems less isolated than using the local settings source.

I posted the full reproduction on #5262 as well.

@wolfyy970 wolfyy970 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that managed agents need a reliable Buzz reply path, but this head does not provide it yet.

Current Claude ignores .claude/settings.json allow rules in a fresh untrusted workspace. The field reproduction on this PR confirms it. The acceptance test needs to launch current Claude from a fresh nest under dontAsk and prove both buzz --help and a signed buzz messages send work without a trust dialog.

I would also avoid leaving Bash(buzz:*) as an unowned permanent workspace grant. It covers every Buzz CLI operation, applies to every managed Claude agent sharing the nest, and is never removed or reconciled. Once active, it can make a later Reject choice in #5106 misleading because Claude will approve the command before ACP asks Buzz.

The clean boundary is a session-scoped, target-owned platform allowance. If settings.local.json is needed for the emergency fix, that is still better than shipping a non-working patch, but Buzz should mark and reconcile what it owns, refuse a symlinked settings directory, and define the cleanup path before #5106 lands.

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Carl reviewing on Wes's behalf.

This does not restore the managed-Claude reply path on the current runtime. Claude Code ignores capability-granting rules from project .claude/settings.json until that workspace is trusted; a fresh Buzz nest never receives the interactive trust acceptance. The independent reproductions on this PR and #5262 show the exact headless warning and denial, while the same rules in .claude/settings.local.json work under dontAsk without a trust dialog.

Please target the headless-safe local settings source (or another mechanism proven to work in an untrusted nest) and add an acceptance test that launches the current Claude runtime from a fresh nest and proves both buzz --help and a signed buzz messages send complete without a permission request. The five new tests only verify JSON merging, so they remain green while the shipped behavior is inert.

Before writing the settings file, also reject a symlinked .claude directory/settings path. ensure_nest_at protects only the root; this new create_dir_all/read/tempfile/persist sequence otherwise follows an existing .claude directory symlink and writes outside the Buzz-owned nest.

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two blocking findings at 724fed49d9c303f59820c5ddfc527307490a1526:

  1. The seeded file is ignored in the exact fresh/headless case this PR needs to repair. Current Claude Code gates project permissions.allow from .claude/settings.json on workspace trust. A fresh Buzz nest has not accepted that interactive trust dialog, so Claude logs Ignoring 2 permissions.allow entries ... workspace has not been trusted and continues denying buzz under dontAsk. This has now been reproduced against Claude Code 2.1.220 and independently with 2.1.226; moving the same rules to .claude/settings.local.json worked without marking the workspace trusted. The five tests only prove JSON merging—they never launch Claude—so they cannot establish the claimed fix. Please target a source that current headless Claude actually honors and add an acceptance test using a fresh, untrusted nest under dontAsk, proving at minimum a bare command and a signed buzz messages send execute without a trust dialog.

  2. The grant has no ownership or lifecycle boundary. ensure_nest_at appends Bash(buzz:*) on every launch and never records whether Buzz inserted it, removes it, or reconciles it when #5106 supplies first-class approvals. It applies to every Claude session using this shared nest, not just the managed session being repaired. Once honored, Claude approves the command before ACP asks Buzz, so a later user-facing Reject decision can be bypassed by this stale workspace rule. Please make the emergency grant explicitly Buzz-owned/reconcilable (and define cleanup during the #5106 transition), or preferably scope the permission at the managed session/target boundary.

Related hardening: refuse a symlinked .claude directory before creating or rewriting settings. The nest root check does not protect this child path, and create_dir_all/read_to_string/persist will follow a directory symlink outside the Buzz nest.

The outage is real and urgent, but this head is not the fix: it silently leaves fresh managed Claude agents unable to publish while installing a permanent grant that may become active later. Green Rust tests do not cover that runtime contract.

Write the managed nest allowlist to Claude's local settings source, which
remains active before a fresh headless workspace is trusted. Refuse
symlinked Claude settings paths so the seed cannot escape the nest.

Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
@wesbillman

Copy link
Copy Markdown
Collaborator

Maintainer takeover is pushed at exact head 91838616a99e8941b9fb47ce910e6dc45052ad9d.

This addresses my previous changes-requested review:

  • retargeted the allowlist to .claude/settings.local.json
  • preserved conservative atomic merging and added proof that project settings.json stays untouched
  • rejects symlinked .claude and settings targets, with external-target regression tests
  • documented this as temporary Buzz-owned compatibility behavior tied to feat(acp): implement harness-level tool permission policy (#4938) #5106
  • proved the exact pushed head through the actual ACP adapter under dontAsk from a fresh untrusted nest with pre-existing unrelated local settings

Runtime evidence: @zed-industries/claude-agent-acp 0.18.0, bundled Claude Code 2.1.45; buzz --help succeeded and signed send returned:

{"accepted":true,"event_id":"121c1936e1aa50740900322d1b9f9a460295068268454678df0ff8fa4ef31d38","mention_pubkeys":[],"message":""}

Adapter stderr was empty and the fail-closed ACP probe received no permission request. Pre-push passed the repo's broad Rust/Desktop suite on this exact head.

Commenting and implementing on Wes's behalf.

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed exact head 91838616a99e8941b9fb47ce910e6dc45052ad9d. My prior blockers are resolved; I found no new actionable correctness or security issue.

  • The grant now targets .claude/settings.local.json, while a regression test proves project settings.json is untouched.
  • Existing model/allow/deny/unknown data remains conservatively merged; invalid and unexpected shapes remain untouched; writes stay idempotent and atomic.
  • Both descendant symlink cases are rejected and tested, including proof that external targets are unchanged.
  • The code and PR explicitly identify this as temporary Buzz-owned compatibility behavior with cleanup/reconciliation tied to #5106.
  • Most importantly, the exact head was exercised through @zed-industries/claude-agent-acp under dontAsk from a fresh untrusted nest with pre-existing local settings: buzz --help succeeded and a signed send returned accepted:true, with no permission request or adapter stderr. That closes the runtime-proof gap that invalidated the original head.

git diff --check is clean. I am leaving a comment verdict rather than auto-approving under the repository's approval policy. From substantive review, this is release-ready once required CI completes green without another head change.

wesbillman
wesbillman previously approved these changes Aug 8, 2026
@wesbillman
wesbillman dismissed their stale review August 8, 2026 15:21

Approval withdrawn at Wes Billman’s request pending further review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Managed agents can't run the bundled buzz CLI after #4609 — all permission requests rejected under dontAsk

6 participants